package com.lianqi.emcpframework.logging.context;

import java.io.PrintWriter;
import java.io.Serializable;
import java.io.StringWriter;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;

import com.alibaba.fastjson.JSON;
import com.lianqi.emcpframework.logging.annotation.ActionType;
import com.lianqi.emcpframework.logging.annotation.InvokingLogger;
import com.lianqi.emcpframework.utils.AssembleTools;

import lombok.Getter;
import lombok.Setter;

/**
 * @program emcp-framework
 * @ClassName InvokingLoggerContext
 * @description: 方法访问日志定义
 * @author: sky
 * @create: 2018/11/17 14:51
 */
@Getter
@Setter
public class InvokingLoggerContext {

    /**
     * 请求id
     */
    private String id;
    /**
     * 客户端ip
     */
    private String ip;
    /**
     * 请求url
     */
    private String url;
    /**
     * 请求操作
     *
     * @see InvokingLogger#value()
     */
    private String action;
    /**
     * 包含操作类型
     * 
     * @see ActionType#getValue()
     */
    private String actionTypes;
    /**
     * 操作描述
     *
     * @see InvokingLogger#describe()
     */
    private String describe;
    /**
     * 访问对应的方法
     */
    private Method method;
    /**
     * 访问对应的类
     */
    private Class target;
    /**
     * 请求参数 方法参数 key为参数名 value为参数值
     */
    private Map<String, Object> parameters;
    /**
     * 请求头
     */
    private Map<String, String> requestHeaders;
    /**
     * 请求方法 GET POST等
     */
    private String httpMethod;
    /**
     * 返回值
     */
    private Object response;
    /**
     * 请求时间戳
     */
    private long requestTime;
    /**
     * 返回时间戳
     */
    private long responseTime;
    /**
     * 异常
     */
    private Throwable exception;

    /**
     * 自定义属性
     */
    private LinkedHashMap<String, Object> customAttributes;

    /**
     * 转换 为map格式
     *
     * @param noSerialExchange
     * @return
     */
    public Map<String, Object> toSimpleMap(Function<Object, Serializable> noSerialExchange, List<String> exclude) {
        return toSimpleMap(noSerialExchange, exclude, new LinkedHashMap<>());
    }

    /**
     * 转换 为map格式
     *
     * @param noSerialExchange
     * @return
     */
    public Map<String, Object> toSimpleMap(Function<Object, Serializable> noSerialExchange) {
        return toSimpleMap(noSerialExchange, null, new LinkedHashMap<>());
    }

    /**
     * 转换为map格式
     *
     * @param objectFilter
     * @param map
     * @return
     */
    private Map<String, Object> toSimpleMap(Function<Object, Serializable> objectFilter, List<String> exclude,
        Map<String, Object> map) {
        putIfNecessary("requestId", id, exclude, map);
        putIfNecessary("ip", ip, exclude, map);
        putIfNecessary("url", url, exclude, map);
        putIfNecessary("action", action, exclude, map);
        putIfNecessary("actionTypes", actionTypes, exclude, map);
        putIfNecessary("describe", describe, exclude, map);
        if (method != null) {
            String[] paramNames = parameters.keySet().toArray(new String[parameters.size()]);
            Class[] paramTypes = method.getParameterTypes();
            putIfNecessary("method", AssembleTools.assembleMethodDefinition(method.getName(), paramNames, paramTypes),
                exclude, map);
        }
        putIfNecessary("target", target != null ? target.getName() : "", exclude, map);
        Map<String, Object> paramValues = new LinkedHashMap<>(parameters);
        if (objectFilter != null) {
            paramValues.entrySet().forEach(entry -> {
                if (entry.getValue() != null) {
                    entry.setValue(objectFilter.apply(entry.getValue()));
                }
            });
        }
        putIfNecessary("parameters", JSON.toJSONString(paramValues), exclude, map);
        putIfNecessary("requestHeaders", requestHeaders, exclude, map);
        putIfNecessary("httpMethod", httpMethod, exclude, map);
        if (objectFilter != null) {
            putIfNecessary("response", objectFilter.apply(response), exclude, map);
        } else {
            putIfNecessary("response", JSON.toJSONString(response), exclude, map);
        }
        SimpleDateFormat dateformat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        putIfNecessary("requestTime", dateformat.format(requestTime), exclude, map);
        putIfNecessary("responseTime", dateformat.format(responseTime), exclude, map);
        putIfNecessary("cost", responseTime - requestTime, exclude, map);
        if (exception != null) {
            StringWriter writer = new StringWriter();
            exception.printStackTrace(new PrintWriter(writer));
            putIfNecessary("exception", writer.toString(), exclude, map);
        }
        if (customAttributes != null) {
            if (objectFilter != null) {
                customAttributes.entrySet().forEach(entry -> {
                    if (entry.getValue() != null) {
                        entry.setValue(objectFilter.apply(entry.getValue()));

                    }
                });
            }
        }
        putIfNecessary("custom", customAttributes, exclude, map);
        return map;
    }

    private void putIfNecessary(String key, Object value, List<String> exclude, Map<String, Object> map) {
        if (!shouldSkip(key, exclude)) {
            map.put(key, value);
        }
    }

    private boolean shouldSkip(String key, List<String> exclude) {
        if (exclude == null) {
            return false;
        }
        return exclude.stream().anyMatch(excludeKey -> excludeKey.equals(key));
    }

}
